-
Notifications
You must be signed in to change notification settings - Fork 1
chore: add logging to 7tv cosmetics flow #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughReplaced per-chatroom 7TV sockets with a shared SharedStvWebSocket path, added telemetry helpers and gated verbose logging, introduced channel cosmetics fetch and cosmetics store test hooks, expanded diagnostics across STV lifecycle and opcode handling, and changed SharedStvWebSocket.addChatroom signature. (39 words) Changes
Sequence Diagram(s)sequenceDiagram
participant UI as App / Chat UI
participant Pre as Preload (app.stv)
participant Shared as SharedStvWebSocket
participant ChatProv as ChatProvider
participant Store as CosmeticsStore
rect rgba(230,245,230,0.25)
Note over Pre,Shared: Channel cosmetics can be fetched (getChannelCosmetics) or arrive via shared WS events
end
UI->>Pre: app.stv.getChannelCosmetics(channelId)
Pre-->>UI: cosmetics payload (badges, paints)
UI->>ChatProv: open channel -> provide cosmetics (if returned)
ChatProv->>Store: addCosmetics (guarded logs)
UI->>Shared: register chatroom (addChatroom with chatroomData)
Shared-->>Shared: subscribe per-chatroom events (telemetry)
Shared->>Shared: receive message (cosmetic.create / entitlement.create / entitlement.delete)
alt event maps to chatroom
Shared->>ChatProv: route event (chatroomId)
ChatProv->>Store: addUserStyle / removeUserStyle (guarded)
else global/broadcast
Shared->>Store: broadcast event (chatroomId = null)
Store->>Store: update state & expose test hooks
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (8)
src/renderer/src/providers/CosmeticsProvider.jsx (3)
15-22: Gate verbose logs behind a debug flag and use console.debugAvoid always-on logging in the renderer; gate it with RENDERER_VITE_* and demote to debug.
Apply this diff:
- console.log( + if (import.meta.env.DEV || import.meta.env.RENDERER_VITE_DEBUG_STV === 'true') console.debug( `[CosmeticsStore] Upserting style for ${transformedUsername}`, { badgeId: userStyle?.style?.badge_id, paintId: userStyle?.style?.paint_id, entitlementId: body?.object?.id, }, );
66-72: Same: gate catalog update logsKeep cosmetics catalog logs behind a flag to reduce noise in production.
- console.log( + if (import.meta.env.DEV || import.meta.env.RENDERER_VITE_DEBUG_STV === 'true') console.debug( `[CosmeticsStore] Updating global cosmetics catalog`, { badges: body?.badges?.length, paints: body?.paints?.length, }, );
12-13: Normalize usernames consistently (hyphen vs underscore)You store with toLowerCase() only, but producer code (ChatProvider) replaces “-” → “_”. This can cause cache misses on getUserStyle.
- const transformedUsername = username.toLowerCase(); + const transformedUsername = username.replaceAll("-", "_").toLowerCase();- const transformedUsername = username.toLowerCase(); + const transformedUsername = username.replaceAll("-", "_").toLowerCase();Also applies to: 49-50
src/renderer/src/providers/ChatProvider.jsx (4)
1896-1904: Make 7TV “received event” logs debug‑gatedThis fires for every WS event; keep it dev/debug only.
- console.log( + if (import.meta.env.DEV || import.meta.env.RENDERER_VITE_DEBUG_STV === 'true') console.debug( `[ChatProvider] Received 7TV event ${type} for ${chatroomId ?? 'broadcast'}`, { badgeCount: body?.badges?.length, paintCount: body?.paints?.length, entitlementUser: body?.object?.user?.username, }, );
1912-1918: Gate cosmetic catalog logsReduce renderer noise; keep under RENDERER_VITE_DEBUG_STV.
- console.log( + if (import.meta.env.DEV || import.meta.env.RENDERER_VITE_DEBUG_STV === 'true') console.debug( `[ChatProvider] Applying cosmetic catalog update for ${chatroomId ?? 'all chatrooms'}`, { badges: body?.badges?.length, paints: body?.paints?.length, }, );
1922-1924: Avoid duplicating username canonicalization hereCosmeticsProvider should own normalization; pass the raw Kick username and let the store normalize consistently.
- const transformedUsername = username?.replaceAll("-", "_").toLowerCase(); + const transformedUsername = username; // normalization handled inside CosmeticsProvider
1924-1931: Gate entitlement processing logsSame rationale: keep these as debug.
- console.log( + if (import.meta.env.DEV || import.meta.env.RENDERER_VITE_DEBUG_STV === 'true') console.debug( `[ChatProvider] Processing entitlement for ${transformedUsername || 'unknown user'}`, { badgeId: body?.object?.user?.style?.badge_id, paintId: body?.object?.user?.style?.paint_id, chatroomId, }, );utils/services/seventv/sharedStvWebSocket.js (1)
177-179: Gate shared 7TV logs behind a single DEBUG flag; use console.debugThese logs are frequent. Add a module‑level DEBUG flag (VITE_* per guidelines for shared code) and switch to console.debug to avoid production noise.
- console.log( + if (import.meta.env.VITE_DEBUG_7TV === 'true' || (typeof process !== 'undefined' && process.env.NODE_ENV === 'development')) console.debug( `[Shared7TV]: Registered chatroom ${chatroomId} (kick=${channelKickID}, stvUser=${stvId}, stvSet=${stvEmoteSetId})`, );- console.log( + if (import.meta.env.VITE_DEBUG_7TV === 'true' || (typeof process !== 'undefined' && process.env.NODE_ENV === 'development')) console.debug( `[Shared7TV]: Preparing subscriptions for chatroom ${chatroomId} (kick=${chatroomData.channelKickID}, emoteSet=${chatroomData.stvEmoteSetId})`, );- console.log( + if (import.meta.env.VITE_DEBUG_7TV === 'true' || (typeof process !== 'undefined' && process.env.NODE_ENV === 'development')) console.debug( `[Shared7TV]: Cosmetic subscription already active for Kick channel ${channelKickID} (chatroom ${chatroomId})`, );- console.log( + if (import.meta.env.VITE_DEBUG_7TV === 'true' || (typeof process !== 'undefined' && process.env.NODE_ENV === 'development')) console.debug( `[Shared7TV]: Entitlement subscription already active for Kick channel ${channelKickID} (chatroom ${chatroomId})`, );- console.log( + if (import.meta.env.VITE_DEBUG_7TV === 'true' || (typeof process !== 'undefined' && process.env.NODE_ENV === 'development')) console.debug( `[Shared7TV]: Received ${type} for ${chatroomId === null ? 'broadcast' : `chatroom ${chatroomId}`}`, { channelContext: body?.context || body?.condition || null, entitlementUser: body?.object?.user?.username, }, );- console.log( + if (import.meta.env.VITE_DEBUG_7TV === 'true' || (typeof process !== 'undefined' && process.env.NODE_ENV === 'development')) console.debug( `[Shared7TV]: Forwarding cosmetic catalog update to ${chatroomId === null ? 'all chatrooms' : chatroomId}`, { badgeCount: cosmetics?.badges?.length, paintCount: cosmetics?.paints?.length, }, );- console.log( + if (import.meta.env.VITE_DEBUG_7TV === 'true' || (typeof process !== 'undefined' && process.env.NODE_ENV === 'development')) console.debug( `[Shared7TV]: Forwarding entitlement for ${body?.object?.user?.username || 'unknown user'}`, { chatroomId, badgeId: body?.object?.user?.style?.badge_id, paintId: body?.object?.user?.style?.paint_id, }, );- console.log( + if (import.meta.env.VITE_DEBUG_7TV === 'true' || (typeof process !== 'undefined' && process.env.NODE_ENV === 'development')) console.debug( `[Shared7TV]: Unable to directly map ${type} to a chatroom, broadcasting`, { contextId, knownChatrooms: Array.from(this.chatrooms.values()).map((data) => data.channelKickID), }, );Also applies to: 405-407, 485-487, 516-518, 592-599, 628-634, 649-656, 696-704
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/renderer/src/providers/ChatProvider.jsx(1 hunks)src/renderer/src/providers/CosmeticsProvider.jsx(2 hunks)utils/services/seventv/sharedStvWebSocket.js(9 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**
📄 CodeRabbit inference engine (AGENTS.md)
**: Use electron-vite + React conventions instead of raw Electron patterns
When unsure, consult electron-vite/Electron/Vite docs (Context7) to confirm patterns
Omit empty sections in release notes
Files:
utils/services/seventv/sharedStvWebSocket.jssrc/renderer/src/providers/CosmeticsProvider.jsxsrc/renderer/src/providers/ChatProvider.jsx
!dist/**
📄 CodeRabbit inference engine (AGENTS.md)
Do not commit built installers/artifacts in dist/
Files:
utils/services/seventv/sharedStvWebSocket.jssrc/renderer/src/providers/CosmeticsProvider.jsxsrc/renderer/src/providers/ChatProvider.jsx
src/renderer/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/renderer/src/**/*.{ts,tsx,js,jsx}: Do not use direct Node APIs in the renderer
Use ipcRenderer.invoke('channel', payload) for request/response from renderer
Use import.meta.env.RENDERER_VITE_* for renderer-scoped config
Avoid bare KT_* env vars in the renderer; use RENDERER_VITE_KT_* instead
Files:
src/renderer/src/providers/CosmeticsProvider.jsxsrc/renderer/src/providers/ChatProvider.jsx
src/{renderer/src,preload}/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Resolve static assets via Vite URLs (new URL('./asset', import.meta.url))
Files:
src/renderer/src/providers/CosmeticsProvider.jsxsrc/renderer/src/providers/ChatProvider.jsx
src/{main,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}
📄 CodeRabbit inference engine (AGENTS.md)
For fire-and-forget events, use ipcRenderer.send and ipcMain.on with namespaced channels like 'app:settings:get'
Files:
src/renderer/src/providers/CosmeticsProvider.jsxsrc/renderer/src/providers/ChatProvider.jsx
src/{main,preload,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}
📄 CodeRabbit inference engine (AGENTS.md)
Use shared non-secret variables via import.meta.env.VITE_*
Files:
src/renderer/src/providers/CosmeticsProvider.jsxsrc/renderer/src/providers/ChatProvider.jsx
🧬 Code graph analysis (3)
utils/services/seventv/sharedStvWebSocket.js (1)
utils/services/seventv/stvWebsocket.js (5)
body(13-13)cosmetics(3-6)data(17-17)data(37-37)data(130-130)
src/renderer/src/providers/CosmeticsProvider.jsx (4)
src/renderer/src/components/Messages/Message.jsx (1)
userStyle(44-44)utils/services/seventv/sharedStvWebSocket.js (1)
body(17-17)src/main/index.js (1)
body(720-720)utils/services/seventv/stvWebsocket.js (1)
body(13-13)
src/renderer/src/providers/ChatProvider.jsx (3)
utils/services/seventv/sharedStvWebSocket.js (1)
body(17-17)utils/services/seventv/stvWebsocket.js (1)
body(13-13)src/renderer/src/providers/CosmeticsProvider.jsx (1)
useCosmeticsStore(3-102)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test-builds (windows-latest)
🔇 Additional comments (1)
utils/services/seventv/sharedStvWebSocket.js (1)
136-153: Avoid require() in renderer-bundled code — use a renderer-safe tracer lookupFound require('@opentelemetry/api') in utils/services/seventv/sharedStvWebSocket.js (lines 136–153). require() can break Vite/ESM renderer bundles — replace with a ChatProvider-style window lookup and fallback:
const getTracer = () => { try { const tracerApi = (typeof window !== 'undefined' && (window.__KT_TRACE_API__?.trace || window.__KT_TRACER__?.trace)) || null; return tracerApi?.getTracer?.('kicktalk-shared-7tv-websocket', '1.0.0'); } catch { return null; } }; const tracer = getTracer() || { startSpan: () => ({ end() {}, setStatus() {}, recordException() {}, addEvent() {}, setAttribute() {}, setAttributes() {} }) };Confirm whether this file is bundled into the renderer; if it is, apply the replacement.
…unctions Added detailed diagnostic logging throughout 7TV cosmetics flow and test functions to identify where the fetch → receive → store → apply chain breaks.
🔍 7TV Cosmetics Diagnostic FindingsI've conducted a comprehensive investigation into why 7TV cosmetics (badges/paints) are not appearing for users. Through systematic diagnostic logging and testing, I've identified the root cause and confirmed the solution path. 🎯 Root Cause Identified7TV cosmetics are not appearing because KickTalk is missing initial API calls to fetch existing cosmetics. The WebSocket system only provides real-time updates, not initial data. 📊 Diagnostic Evidence✅ What's Working:
❌ What's Missing:
🔬 Technical AnalysisWebSocket Flow Analysis:Store/Apply Chain Test:Real User Cosmetics:📚 Research Findings (via DeepWiki)Consulted 7TV's official documentation and confirmed:
🛠️ Solution PathBased on the findings, here's what needs to be implemented: 1. Add Initial Cosmetics Fetching// On connection, fetch global cosmetics catalog
const globalCosmetics = await fetch7TVCosmetics();
cosmeticsStore.addCosmetics(globalCosmetics);
// For each user, fetch their specific cosmetics
const userCosmetics = await fetch7TVUserInventory(userId);
cosmeticsStore.addUserStyle(username, userCosmetics);2. Fix User-Scoped SubscriptionsCurrently subscribing to channel-scoped cosmetic events: // Current (channel-scoped)
condition: { platform: "KICK", ctx: "channel", id: "972889" }
// Needed (user-scoped)
condition: { object_id: stvUserId }3. API Endpoints to Implement
🧪 Testing AddedFor future debugging, added:
💡 Next Steps
✨ ImpactOnce the initial API calls are implemented, users will see:
The diagnostic framework is now in place to verify the fix works correctly. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🧹 Nitpick comments (8)
src/renderer/src/providers/ChatProvider.jsx (3)
1514-1525: Gate high-volume 7TV event logs behind a debug switch.These logs run for every WS event and can flood consoles; also they include user identifiers. Wrap with a debug guard like the presence logs.
- console.log(`[ChatProvider] Received 7TV event from shared WebSocket`, { + if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[ChatProvider] Received 7TV event from shared WebSocket`, { type, chatroomId, hasBody: !!body, entitlementUser: body?.object?.user?.username, badgeId: body?.object?.user?.style?.badge_id, paintId: body?.object?.user?.style?.paint_id, badgeCount: body?.badges?.length, paintCount: body?.paints?.length - }); + }); - console.log(`[ChatProvider] Routing event ${type} to specific chatroom: ${chatroomId}`); + if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[ChatProvider] Routing event ${type} to specific chatroom: ${chatroomId}`); - console.log(`[ChatProvider] Broadcasting event ${type} to all chatrooms (${chatrooms.length} total)`); + if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[ChatProvider] Broadcasting event ${type} to all chatrooms (${chatrooms.length} total)`);Also applies to: 1527-1527, 1531-1531
1913-1920: Gate diagnostic per-event log or make it debug-only.Same rationale as above; large volume and includes identifiers.
- console.log( + if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log( `[ChatProvider] Received 7TV event ${type} for ${chatroomId ?? 'broadcast'}`, { badgeCount: body?.badges?.length, paintCount: body?.paints?.length, entitlementUser: body?.object?.user?.username, }, );
4092-4094: Minor: align presence interval with comment or update comment.The comment says “every 2 minutes” but the interval is 1 minute. Pick one.
- 1 * 60 * 1000, + 2 * 60 * 1000,utils/services/seventv/sharedStvWebSocket.js (4)
346-361: Gate subscription/setup logs or sample them.These run on every (re)connect; make them conditional to avoid noise.
- console.log(`[Shared7TV]: Starting subscription to all events for ${this.chatrooms.size} chatrooms`); + if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[Shared7TV]: Starting subscription to all events for ${this.chatrooms.size} chatrooms`); ... - console.log(`[Shared7TV]: Finished subscribing to all events`); + if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[Shared7TV]: Finished subscribing to all events`); ... - console.log(`[Shared7TV]: Setting up message handler`); + if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[Shared7TV]: Setting up message handler`); ... - console.log(`[Shared7TV]: Message handler setup complete`); + if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[Shared7TV]: Message handler setup complete`);Also applies to: 363-366
619-630: Reduce per-message logging; add debug guard and/or sampling.Logging raw payloads and every opcode will spam consoles and degrade perf.
- this.chat.onmessage = (event) => { - console.log(`[Shared7TV]: Raw WebSocket message received:`, event.data); + this.chat.onmessage = (event) => { + const __dbg = !!window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.(); + if (__dbg) console.log(`[Shared7TV]: Raw WebSocket message received`); try { const msg = JSON.parse(event.data); - console.log(`[Shared7TV]: Parsed message:`, { + if (__dbg) console.log(`[Shared7TV]: Parsed message:`, { op: msg?.op, type: msg?.d?.type, hasBody: !!msg?.d?.body, - fullMessage: msg + // Avoid dumping full message outside deep debug + // fullMessage: msg });Optionally, sample with a counter (e.g., log every Nth heartbeat/dispatch).
Also applies to: 632-743
753-766: Consider redacting or gating full cosmetic/entitlement bodies.Full bodies include user identifiers; prefer debug-only or field-level logs already present.
888-896: Map cosmetic/entitlement events to chatrooms when context.id matches.You always broadcast; use context.id to route when possible.
- if (type.startsWith("cosmetic.") || type.startsWith("entitlement.")) { - const contextId = body?.context?.id || body?.condition?.id || null; - console.log( - `[Shared7TV]: Unable to directly map ${type} to a chatroom, broadcasting`, - { - contextId, - knownChatrooms: Array.from(this.chatrooms.values()).map((data) => data.channelKickID), - }, - ); - } + if (type.startsWith("cosmetic.") || type.startsWith("entitlement.")) { + const contextId = body?.context?.id || body?.condition?.id; + if (contextId != null) { + for (const [id, data] of this.chatrooms) { + if (String(data.channelKickID) === String(contextId)) { + return id; + } + } + } + if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) { + console.log(`[Shared7TV]: Broadcasting ${type} (no chatroom match)`, { + contextId, + knownChatrooms: Array.from(this.chatrooms.values()).map((d) => d.channelKickID), + }); + } + }utils/services/seventv/stvWebsocket.js (1)
355-466: Add a debug guard for per-opcode logs.Mirrors the shared WS suggestion; these fire frequently.
- switch (msg?.op) { + const __dbg = !!window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.(); + switch (msg?.op) { case 0: - console.log(`[7TV]: Dispatch event received for channel ${this.channelKickID}`, { type: msg?.d?.type }); + if (__dbg) console.log(`[7TV]: Dispatch event received for channel ${this.channelKickID}`, { type: msg?.d?.type }); try { window.app?.telemetry?.recordWebSocketMessage?.(`7tv_${this.channelKickID}`, 'dispatch', msg?.d?.type); } catch (_) {} break; ...
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/renderer/src/providers/ChatProvider.jsx(6 hunks)src/renderer/src/providers/CosmeticsProvider.jsx(4 hunks)utils/services/seventv/sharedStvWebSocket.js(15 hunks)utils/services/seventv/stvWebsocket.js(3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/renderer/src/providers/CosmeticsProvider.jsx
🧰 Additional context used
📓 Path-based instructions (6)
**
📄 CodeRabbit inference engine (AGENTS.md)
**: Use electron-vite + React conventions instead of raw Electron patterns
When unsure, consult electron-vite/Electron/Vite docs (Context7) to confirm patterns
Omit empty sections in release notes
Files:
utils/services/seventv/stvWebsocket.jsutils/services/seventv/sharedStvWebSocket.jssrc/renderer/src/providers/ChatProvider.jsx
!dist/**
📄 CodeRabbit inference engine (AGENTS.md)
Do not commit built installers/artifacts in dist/
Files:
utils/services/seventv/stvWebsocket.jsutils/services/seventv/sharedStvWebSocket.jssrc/renderer/src/providers/ChatProvider.jsx
src/renderer/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/renderer/src/**/*.{ts,tsx,js,jsx}: Do not use direct Node APIs in the renderer
Use ipcRenderer.invoke('channel', payload) for request/response from renderer
Use import.meta.env.RENDERER_VITE_* for renderer-scoped config
Avoid bare KT_* env vars in the renderer; use RENDERER_VITE_KT_* instead
Files:
src/renderer/src/providers/ChatProvider.jsx
src/{renderer/src,preload}/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Resolve static assets via Vite URLs (new URL('./asset', import.meta.url))
Files:
src/renderer/src/providers/ChatProvider.jsx
src/{main,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}
📄 CodeRabbit inference engine (AGENTS.md)
For fire-and-forget events, use ipcRenderer.send and ipcMain.on with namespaced channels like 'app:settings:get'
Files:
src/renderer/src/providers/ChatProvider.jsx
src/{main,preload,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}
📄 CodeRabbit inference engine (AGENTS.md)
Use shared non-secret variables via import.meta.env.VITE_*
Files:
src/renderer/src/providers/ChatProvider.jsx
🧬 Code graph analysis (2)
utils/services/seventv/sharedStvWebSocket.js (1)
utils/services/seventv/stvWebsocket.js (5)
body(13-13)cosmetics(3-6)data(17-17)data(37-37)data(130-130)
src/renderer/src/providers/ChatProvider.jsx (3)
utils/services/seventv/sharedStvWebSocket.js (2)
body(17-17)cosmetics(4-7)utils/services/seventv/stvWebsocket.js (2)
body(13-13)cosmetics(3-6)src/renderer/src/providers/CosmeticsProvider.jsx (1)
useCosmeticsStore(3-217)
🪛 Biome (2.1.2)
src/renderer/src/providers/ChatProvider.jsx
[error] 1936-1937: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.
The declaration is defined in this switch clause:
Safe fix: Wrap the declaration in a block.
(lint/correctness/noSwitchDeclarations)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test-builds (windows-latest)
🔇 Additional comments (5)
src/renderer/src/providers/ChatProvider.jsx (2)
554-556: Nice: presence debug gated behind telemetry util.Prevents noisy logs in production.
1643-1646: Good: individual 7TV connections explicitly disabled with clear log.Avoids duplicate sockets and clarifies shared-connection intent.
Also applies to: 2204-2207
utils/services/seventv/sharedStvWebSocket.js (2)
177-180: Helpful registration log.Clear, low-risk addition that aids troubleshooting.
511-515: Subscription diagnostics are clear and helpful.Good visibility around idempotency and readyState checks.
Also applies to: 521-523, 535-539, 546-549, 555-556, 569-573
utils/services/seventv/stvWebsocket.js (1)
221-227: Telemetry on errors/retries looks good.Provides useful context (attempt, delay, channel).
Also applies to: 233-237, 246-255
| `[ChatProvider] Processing entitlement for ${transformedUsername || 'unknown user'}`, | ||
| { | ||
| badgeId: body?.object?.user?.style?.badge_id, | ||
| paintId: body?.object?.user?.style?.paint_id, | ||
| chatroomId, | ||
| }, | ||
| ); | ||
| const addUserStyle = useCosmeticsStore?.getState()?.addUserStyle; | ||
| if (addUserStyle) { | ||
| console.log(`[ChatProvider] Calling CosmeticsStore.addUserStyle for ${transformedUsername}`); | ||
| addUserStyle(transformedUsername, body); | ||
| } else { | ||
| console.error(`[ChatProvider] CosmeticsStore.addUserStyle method not available!`); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Username normalization may diverge from store lookups.
You store under username.replaceAll('-', '_').toLowerCase() but CosmeticsStore.getUserStyle() normalizes to lowercase only. This can break lookups for hyphenated names.
Prefer a shared normalizer used by both writer and reader (e.g., keep original username from KICK connection and only lowercase), or update getUserStyle to mirror the same normalization.
🏁 Script executed:
#!/bin/bash
# Find all places replacing '-' with '_' for usernames to assess consistency
rg -nP "replaceAll\(\s*['\"]-['\"],\s*['\"]_['\"]\s*\).*username" -C2 --type=ts --type=tsx --type=js --type=jsxLength of output: 27
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "1) Searching for replaceAll occurrences (no type flags)..."
rg -n -C2 "replaceAll\(" || true
echo
echo "2) Searching for any replace( occurrences (showing context)..."
rg -n -C2 "replace\(" || true
echo
echo "3) Searching for transformedUsername occurrences..."
rg -n -C2 "transformedUsername" || true
echo
echo "4) Searching for getUserStyle occurrences..."
rg -n -C2 "getUserStyle" || true
echo
echo "5) Searching for addUserStyle occurrences..."
rg -n -C2 "addUserStyle" || true
echo
echo "6) Searching for CosmeticsStore occurrences..."
rg -n -C2 "CosmeticsStore" || true
echo
echo "7) Show ChatProvider.jsx snippet (lines 1900-1980) if file exists..."
if [ -f src/renderer/src/providers/ChatProvider.jsx ]; then
sed -n '1900,1980p' src/renderer/src/providers/ChatProvider.jsx || true
else
echo "src/renderer/src/providers/ChatProvider.jsx not found"
fiLength of output: 47048
Inconsistent username normalization — hyphenated usernames will miss stored cosmetics
ChatProvider normalizes KICK usernames with username?.replaceAll("-", "_").toLowerCase() (src/renderer/src/providers/ChatProvider.jsx:1949) but CosmeticsProvider.addUserStyle/getUserStyle only call username.toLowerCase() (src/renderer/src/providers/CosmeticsProvider.jsx:32,75). Result: "foo-bar" vs "foo_bar" keys are inconsistent and lookups can fail.
Fix: centralize normalization (e.g., utils/normalizeUsername that replaces '-'→'_' and lowercases) and use it in both addUserStyle and getUserStyle — or have getUserStyle mirror ChatProvider's replaceAll behavior before lookup.
🤖 Prompt for AI Agents
In src/renderer/src/providers/ChatProvider.jsx around lines 1951-1964, username
normalization is inconsistent with CosmeticsProvider leading to hyphenated
usernames ("foo-bar") not matching stored keys ("foo_bar"); create a single
utils function (e.g., utils/normalizeUsername) that replaces '-' with '_' and
lowercases the string, then use that function in ChatProvider where usernames
are processed and in CosmeticsProvider's addUserStyle and getUserStyle (and any
lookup sites) so all keys are normalized identically before storage and lookup.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🧹 Nitpick comments (3)
src/renderer/src/providers/ChatProvider.jsx (3)
1514-1531: Gate verbose 7TV routing logs behind debug flag.These logs fire for every event and chatroom; wrap them with shouldLogDebug to avoid hot‑path noise.
Apply:
- console.log(`[ChatProvider] Received 7TV event from shared WebSocket`, { + if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[ChatProvider] Received 7TV event from shared WebSocket`, { type, chatroomId, hasBody: !!body, entitlementUser: body?.object?.user?.username, badgeId: body?.object?.user?.style?.badge_id, paintId: body?.object?.user?.style?.paint_id, badgeCount: body?.badges?.length, paintCount: body?.paints?.length - }); + }); ... - console.log(`[ChatProvider] Routing event ${type} to specific chatroom: ${chatroomId}`); + if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[ChatProvider] Routing event ${type} to specific chatroom: ${chatroomId}`); ... - console.log(`[ChatProvider] Broadcasting event ${type} to all chatrooms (${chatrooms.length} total)`); + if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[ChatProvider] Broadcasting event ${type} to all chatrooms (${chatrooms.length} total)`);
1913-1921: Gate per‑event 7TV logs.Same rationale: avoid hot‑path logging unless debug is enabled.
Apply:
- console.log( + if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log( `[ChatProvider] Received 7TV event ${type} for ${chatroomId ?? 'broadcast'}`, { badgeCount: body?.badges?.length, paintCount: body?.paints?.length, entitlementUser: body?.object?.user?.username, }, );
4087-4102: Presence interval comment mismatches code.Comment says “every 2 minutes,” code runs every 1 minute. Align one of them.
Apply one (doc-only) fix:
- // Send presence updates every 2 minutes + // Send presence updates every 1 minuteOr change interval to
2 * 60 * 1000.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
src/renderer/src/providers/ChatProvider.jsx(6 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
**
📄 CodeRabbit inference engine (AGENTS.md)
**: Use electron-vite + React conventions instead of raw Electron patterns
When unsure, consult electron-vite/Electron/Vite docs (Context7) to confirm patterns
Omit empty sections in release notes
Files:
src/renderer/src/providers/ChatProvider.jsx
src/renderer/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/renderer/src/**/*.{ts,tsx,js,jsx}: Do not use direct Node APIs in the renderer
Use ipcRenderer.invoke('channel', payload) for request/response from renderer
Use import.meta.env.RENDERER_VITE_* for renderer-scoped config
Avoid bare KT_* env vars in the renderer; use RENDERER_VITE_KT_* instead
Files:
src/renderer/src/providers/ChatProvider.jsx
src/{renderer/src,preload}/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Resolve static assets via Vite URLs (new URL('./asset', import.meta.url))
Files:
src/renderer/src/providers/ChatProvider.jsx
src/{main,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}
📄 CodeRabbit inference engine (AGENTS.md)
For fire-and-forget events, use ipcRenderer.send and ipcMain.on with namespaced channels like 'app:settings:get'
Files:
src/renderer/src/providers/ChatProvider.jsx
src/{main,preload,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}
📄 CodeRabbit inference engine (AGENTS.md)
Use shared non-secret variables via import.meta.env.VITE_*
Files:
src/renderer/src/providers/ChatProvider.jsx
!dist/**
📄 CodeRabbit inference engine (AGENTS.md)
Do not commit built installers/artifacts in dist/
Files:
src/renderer/src/providers/ChatProvider.jsx
🧬 Code graph analysis (1)
src/renderer/src/providers/ChatProvider.jsx (3)
utils/services/seventv/stvWebsocket.js (1)
body(13-13)utils/services/seventv/sharedStvWebSocket.js (1)
body(17-17)src/renderer/src/providers/CosmeticsProvider.jsx (1)
useCosmeticsStore(3-217)
🪛 Biome (2.1.2)
src/renderer/src/providers/ChatProvider.jsx
[error] 1929-1930: Duplicate case label.
The first similar label is here:
(lint/suspicious/noDuplicateCase)
[error] 1950-1950: This code will never be reached ...
... because this statement will break the flow of the code beforehand
(lint/correctness/noUnreachable)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test-builds (windows-latest)
🔇 Additional comments (4)
src/renderer/src/providers/ChatProvider.jsx (4)
554-556: Good: noisy presence logs are now debug‑gated.Reduces console spam and perf overhead in normal runs.
1643-1646: ACK: per‑chatroom 7TV sockets disabled.Consistent with the new shared connection path.
4095-4097: Good: presence loop logging is debug‑gated.Keeps the interval loop quiet in production.
1953-1967: Unify username normalization with CosmeticsStore to avoid missed lookups.CosmeticsProvider lowers case only; replacing '-'→'_' here writes a different key and breaks getUserStyle. Pass lowercase only (or centralize a shared normalizer).
Apply:
- const username = body?.object?.user?.connections?.find((c) => c.platform === "KICK")?.username; - const transformedUsername = username?.replaceAll("-", "_").toLowerCase(); - console.log( - `[ChatProvider] Processing entitlement for ${transformedUsername || 'unknown user'}`, + const username = body?.object?.user?.connections?.find((c) => c.platform === "KICK")?.username; + const normalizedUsername = username?.toLowerCase(); + if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log( + `[ChatProvider] Processing entitlement for ${normalizedUsername || 'unknown user'}`, { badgeId: body?.object?.user?.style?.badge_id, paintId: body?.object?.user?.style?.paint_id, chatroomId, }, ); const addUserStyle = useCosmeticsStore?.getState()?.addUserStyle; if (addUserStyle) { - console.log(`[ChatProvider] Calling CosmeticsStore.addUserStyle for ${transformedUsername}`); - addUserStyle(transformedUsername, body); + if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) console.log(`[ChatProvider] Calling CosmeticsStore.addUserStyle for ${normalizedUsername}`); + addUserStyle(normalizedUsername, body); } else { console.error(`[ChatProvider] CosmeticsStore.addUserStyle method not available!`); }Optionally, extract a shared
normalizeUsernamehelper and use it in both ChatProvider and CosmeticsProvider.
- Introduce getTelemetryLevel/getTelemetryDebug that prefer VITE_TELEMETRY_* and fall back to MAIN_/RENDERER_ variants for cross-process consistency - Add shouldLogDebug and gate verbose logs on debug=true or level=VERBOSE - Add guarded request/response/result logs to the main OTEL IPC relay for trace exports - Expose telemetry utils (level/debug/shouldLogDebug) to window in the renderer - Defaults remain NORMAL/false to avoid noise and simplify troubleshooting
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/telemetry/user-analytics.js (1)
548-639: Cleanup leaks: “historical” and “adoption” pruning never triggers (missing user_id/session_end_time/timestamp).
- userBehaviorData entries written in endSession don’t include user_id, timestamp, or session_end_time, but cleanup relies on them.
- featureAdoptionData purge checks data.user_id and data.session_end_time that don’t exist.
Result: unbounded growth over time.
Apply these changes:
- Persist required fields at session end:
endSession(sessionId) { @@ - // Store session data for correlation analysis - userBehaviorData.set(sessionId, { - duration, - actions: session.actions.length, - engagement_rate: session.getEngagementRate(), - satisfaction_score: finalScore, - features_used: Array.from(session.featureUsage.keys()), - error_count: session.errorCount - }); + // Store session data for correlation analysis and cleanup keys + userBehaviorData.set(sessionId, { + user_id: session.userId, + session_start_time: session.startTime, + session_end_time: Date.now(), + duration, + actions: session.actions.length, + engagement_rate: session.getEngagementRate(), + satisfaction_score: finalScore, + features_used: Array.from(session.featureUsage.keys()), + error_count: session.errorCount, + });
- Make cleanup predicates robust:
- // Clean up historical behavior data (keep for 7x longer) + // Clean up historical behavior data (keep for 7x longer) const historicalCutoff = Date.now() - (maxAgeMs * 7); for (const [sessionId, data] of userBehaviorData) { - try { - // Extract timestamp from session ID format: session_timestamp_random - const sessionTimestamp = parseInt(sessionId.split('_')[1]) || 0; - if (sessionTimestamp < historicalCutoff) { - userBehaviorData.delete(sessionId); - cleanedHistoricalData++; - } - } catch (error) { - // If session ID format is unexpected, clean it up anyway if too old - if (data.timestamp && data.timestamp < historicalCutoff) { - userBehaviorData.delete(sessionId); - cleanedHistoricalData++; - } - } + const endedAt = Number(data?.session_end_time) || 0; + const startedAt = Number(data?.session_start_time) || 0; + const refTs = endedAt || startedAt; + if (refTs && refTs < historicalCutoff) { + userBehaviorData.delete(sessionId); + cleanedHistoricalData++; + } } @@ - for (const [userId, features] of featureAdoptionData) { - // Remove adoption data if no recent sessions for this user - const hasRecentSession = Array.from(userBehaviorData.values()) - .some(data => data.user_id === userId && - data.session_end_time > adoptionCutoff); + for (const [userId, features] of featureAdoptionData) { + // Remove adoption data if no recent sessions for this user + const hasRecentSession = Array.from(userBehaviorData.values()) + .some(data => data?.user_id === userId && Number(data?.session_end_time) > adoptionCutoff);Optionally, initialize session_start_time at UserSession construction to make it explicit:
constructor(sessionId, userId = null) { @@ - this.startTime = Date.now(); + this.startTime = Date.now(); // alias: session_start_timesrc/renderer/src/telemetry/webTracing.js (1)
379-393: Do not import @opentelemetry/resources in the renderer (breaks Vite ESM, contradicts file header).Even dynamic import can drag the module into the bundle and trigger version/shape conflicts. You already inject resource attributes in the exported OTLP JSON; drop this import entirely.
- try { - const resMod = await import('@opentelemetry/resources'); - const ResourceCtor = resMod?.Resource || resMod?.default?.Resource; - if (ResourceCtor) { - const extra = new ResourceCtor({ - 'service.name': 'kicktalk-renderer', - 'service.namespace': 'kicktalk', - 'deployment.environment': deploymentEnv || 'development' - }); - if (provider.resource && typeof provider.resource.merge === 'function') { - provider.resource = provider.resource.merge(extra); - } - } - } catch (e) { - try { console.debug('[Renderer OTEL]: Resource injection skipped:', e?.message || e); } catch {} - } + // Skip @opentelemetry/resources in renderer to avoid Vite ESM conflicts. + // Resource attributes are added in the OTLP JSON builder below.
🧹 Nitpick comments (7)
utils/services/connectionManager.js (1)
121-136: Gate Stv handler registration logs behind telemetry debug; downgrade missing-handler warning.These logs will fire for every chatroom and can get noisy. Use shouldLogDebug and prefer console.debug for the “missing handler” case.
Apply this diff and add the helper shown below:
- console.log(`[ConnectionManager] Registering onStvMessage handler`); + if (shouldLogDebug()) console.log(`[ConnectionManager] Registering onStvMessage handler`); this.stvWebSocket.addEventListener("message", handlers.onStvMessage); - } else { - console.warn(`[ConnectionManager] No onStvMessage handler provided!`); + } else { + if (shouldLogDebug()) console.debug(`[ConnectionManager] No onStvMessage handler provided`); } @@ - console.log(`[ConnectionManager] Registering onStvOpen handler`); + if (shouldLogDebug()) console.log(`[ConnectionManager] Registering onStvOpen handler`); @@ - console.log(`[ConnectionManager] Registering onStvConnection handler`); + if (shouldLogDebug()) console.log(`[ConnectionManager] Registering onStvConnection handler`);Add near the top of the module:
// At top-level (once) const shouldLogDebug = globalThis?.window?.__KT_TELEMETRY_UTILS__?.shouldLogDebug || (() => false);src/renderer/src/telemetry/webTracing.js (5)
198-224: Renderer debug helpers OK; apply them consistently to top-level logs.There are many unconditional console.logs earlier (e.g., Lines 32–35, 41, 49–50, 87–88, 120, 176–183, 189–196). Gate them with shouldLogDebug() for parity.
Example:
-console.log('[DEBUG] webTracing.js module loading - telemetry enabled:', telemetryEnabled); +if (shouldLogDebug()) console.log('[DEBUG] webTracing.js module loading - telemetry enabled:', telemetryEnabled);
676-689: Exporter debug logs gated correctly. Consider redacting or truncating identifiers.Not critical, but you can slice traceIds/spanIds to first 8–16 chars for compact logs.
Also applies to: 693-702, 709-720
1083-1111: IPCSimpleSpanProcessor still logs noisily; gate with shouldLogDebug().These logs will emit for every span and can flood consoles.
- console.log('[Renderer OTEL] Span started:', { + if (shouldLogDebug()) console.log('[Renderer OTEL] Span started:', { @@ - console.log('[Renderer OTEL] Span ended → exporting via IPC:', { + if (shouldLogDebug()) console.log('[Renderer OTEL] Span ended → exporting via IPC:', { @@ - console.log('[Renderer OTEL] Span export callback:', { + if (shouldLogDebug()) console.log('[Renderer OTEL] Span export callback:', {Also applies to: 1113-1121, 1127-1135
1414-1601: Dev helpers (emitRendererTestSpan, compareOTLPPayloads) should be dev-only or gated.*Expose these only in DEV or when shouldLogDebug() to avoid accidental use in production.
-// Expose helper to emit a manual renderer test span from DevTools -if (true) { +// Expose helpers only in dev or verbose debug +if (import.meta.env.DEV || shouldLogDebug()) { // ... keep helper definitions ... }
2054-2055: Provider name mismatch breaks forceFlush in verifyGrafanaTraces.You set window.KT_OTEL_PROVIDER earlier; here you read KT_TRACE_PROVIDER.
- const provider = window.__KT_TRACE_PROVIDER__; + const provider = window.__KT_OTEL_PROVIDER__;src/main/index.js (1)
715-719: OTLP IPC relay logs gated properly; add payload size guard to protect main thread.Large payloads can stall the event loop. Reject overly large bodies early.
const body = Buffer.from(JSON.stringify(exportJson), "utf8"); + // Guardrail: reject payloads >5MB + const MAX_BYTES = 5 * 1024 * 1024; + if (body.length > MAX_BYTES) { + console.warn(`[OTEL IPC Relay][${requestId}] Rejecting oversized payload: ${body.length} bytes`); + return { ok: false, reason: "payload_too_large", requestId }; + }Also applies to: 764-767, 776-779, 799-801
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
src/main/index.js(5 hunks)src/renderer/src/telemetry/webTracing.js(5 hunks)src/telemetry/user-analytics.js(8 hunks)utils/services/connectionManager.js(1 hunks)
🧰 Additional context used
📓 Path-based instructions (8)
**
📄 CodeRabbit inference engine (AGENTS.md)
**: Use electron-vite + React conventions instead of raw Electron patterns
When unsure, consult electron-vite/Electron/Vite docs (Context7) to confirm patterns
Omit empty sections in release notes
Files:
src/renderer/src/telemetry/webTracing.jssrc/telemetry/user-analytics.jssrc/main/index.jsutils/services/connectionManager.js
src/renderer/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/renderer/src/**/*.{ts,tsx,js,jsx}: Do not use direct Node APIs in the renderer
Use ipcRenderer.invoke('channel', payload) for request/response from renderer
Use import.meta.env.RENDERER_VITE_* for renderer-scoped config
Avoid bare KT_* env vars in the renderer; use RENDERER_VITE_KT_* instead
Files:
src/renderer/src/telemetry/webTracing.js
src/{renderer/src,preload}/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Resolve static assets via Vite URLs (new URL('./asset', import.meta.url))
Files:
src/renderer/src/telemetry/webTracing.js
src/{main,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}
📄 CodeRabbit inference engine (AGENTS.md)
For fire-and-forget events, use ipcRenderer.send and ipcMain.on with namespaced channels like 'app:settings:get'
Files:
src/renderer/src/telemetry/webTracing.jssrc/main/index.js
src/{main,preload,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}
📄 CodeRabbit inference engine (AGENTS.md)
Use shared non-secret variables via import.meta.env.VITE_*
Files:
src/renderer/src/telemetry/webTracing.jssrc/main/index.js
src/{telemetry,renderer/src/telemetry}/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/{telemetry,renderer/src/telemetry}/**/*.{ts,tsx,js,jsx}: Place OpenTelemetry instrumentation under src/telemetry/** and src/renderer/src/telemetry/**
Configure OTLP HTTP export to Grafana Cloud; read endpoints/keys from env with main-safe prefixes; do not expose secrets to renderer
Files:
src/renderer/src/telemetry/webTracing.jssrc/telemetry/user-analytics.js
!dist/**
📄 CodeRabbit inference engine (AGENTS.md)
Do not commit built installers/artifacts in dist/
Files:
src/renderer/src/telemetry/webTracing.jssrc/telemetry/user-analytics.jssrc/main/index.jsutils/services/connectionManager.js
src/main/**/*.{ts,js,mts,mjs,cjs}
📄 CodeRabbit inference engine (AGENTS.md)
src/main/**/*.{ts,js,mts,mjs,cjs}: From the main process, load the built preload bundle (e.g., join(dirname, '../preload/index.js') or fileURLToPath(new URL('../preload/index.js', import.meta.url)) in ESM)
Keep BrowserWindow webPreferences.contextIsolation: true
Validate and sanitize all inputs received from renderer in main process handlers
Use ipcMain.handle('channel', fn) for request/response handlers in main
Access secrets in main via process.env or MAIN_VITE*; never expose them to renderer
Use import.meta.env.MAIN_VITE* for main-scoped non-secret config
Files:
src/main/index.js
🧠 Learnings (2)
📚 Learning: 2025-09-18T22:47:22.731Z
Learnt from: CR
PR: BP602/KickTalk#0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T22:47:22.731Z
Learning: Applies to src/{telemetry,renderer/src/telemetry}/**/*.{ts,tsx,js,jsx} : Configure OTLP HTTP export to Grafana Cloud; read endpoints/keys from env with main-safe prefixes; do not expose secrets to renderer
Applied to files:
src/main/index.js
📚 Learning: 2025-09-18T22:47:22.731Z
Learnt from: CR
PR: BP602/KickTalk#0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T22:47:22.731Z
Learning: Applies to src/{telemetry,renderer/src/telemetry}/**/*.{ts,tsx,js,jsx} : Place OpenTelemetry instrumentation under src/telemetry/** and src/renderer/src/telemetry/**
Applied to files:
src/main/index.js
🧬 Code graph analysis (3)
src/renderer/src/telemetry/webTracing.js (2)
src/main/index.js (4)
level(150-152)getTelemetryDebug(137-145)shouldLogDebug(159-161)getTelemetryLevel(147-157)src/telemetry/user-analytics.js (4)
level(20-22)getTelemetryDebug(6-15)shouldLogDebug(29-31)getTelemetryLevel(17-27)
src/telemetry/user-analytics.js (3)
src/main/index.js (4)
getTelemetryDebug(137-145)getTelemetryLevel(147-157)level(150-152)shouldLogDebug(159-161)src/renderer/src/telemetry/webTracing.js (4)
getTelemetryDebug(211-219)getTelemetryLevel(199-209)level(202-204)shouldLogDebug(221-223)src/telemetry/tracing.js (1)
level(86-86)
src/main/index.js (2)
src/renderer/src/telemetry/webTracing.js (4)
getTelemetryDebug(211-219)getTelemetryLevel(199-209)level(202-204)shouldLogDebug(221-223)src/telemetry/user-analytics.js (4)
getTelemetryDebug(6-15)getTelemetryLevel(17-27)level(20-22)shouldLogDebug(29-31)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: test-builds (windows-latest)
🔇 Additional comments (9)
src/telemetry/user-analytics.js (7)
310-313: Good: endSession log gated by telemetry.No action needed.
326-329: Good: startSession log gated by telemetry.
354-356: Good: satisfaction log gated by telemetry.
454-456: Good: feature usage log gated by telemetry.
492-494: Good: connection quality log gated by telemetry.
676-686: Good: force-cleanup log gated by telemetry.
5-31: Env-driven debug gating OK — confirm this file isn't imported by rendererReads process.env; ensure module src/telemetry/user-analytics.js is never bundled into or imported by any renderer code. Confirm with:
rg -n -C2 -g 'src/renderer/**' -e 'telemetry[/\]user-analytics' -e 'user-analytics'src/main/index.js (2)
136-162: Main debug helpers align with renderer; good.
136-162: Search and fix legacy MAIN_VITELEMETRY typosEarlier code used MAIN_VITELEMETRY (missing underscore). Search for MAIN_VITELEMETRY, MAINVITELEMETRY, VITELEMETRY (and renderer variants) and normalize them to VITE_TELEMETRY / MAIN_VITE_TELEMETRY to avoid split behavior. Automated sandbox search returned "No files were searched" — verification couldn't be completed; verify locally.
- Remove unnecessary refresh7TVEmotes() call from handleEmoteSetUpdate - WebSocket events already provide updated emote data, API call was redundant - Fixes excessive "Refreshing 7TV emotes..." messages in chat - Add entitlement.delete event handling for proper cosmetic removal - Restore proper Zustand subscription in Message component for live cosmetic updates - Add deduplication for global cosmetic events (cosmetic.create, entitlement.*) - Fix new chatroom 7TV subscription via connectionManager.addChatroom - Add initial cosmetics loading when connecting to chatrooms - Improve diagnostic logging throughout cosmetics flow
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (9)
utils/services/connectionManager.js (1)
603-609: Inconsistent Kick ID passed to 7TV addChatroom (user_id vs id).addChatroom uses streamerData.user_id; here syncSharedStvChatroom passes streamerData.id. Use user_id consistently or subscriptions may target the wrong subject.
this.stvWebSocket.addChatroom( chatroom.id, - chatroom.streamerData?.id, + chatroom.streamerData?.user_id, stvId, stvEmoteSetId, chatroom // Pass the full chatroom data for ID variants );utils/services/seventv/stvWebsocket.js (3)
51-71: Guard optional fields in paint processing.data.stops and data.shadows may be absent; current code can throw on .length.
- if (data.stops.length) { + if (Array.isArray(data.stops) && data.stops.length) { // ... } else { // ... } - if (data.shadows.length) { + if (Array.isArray(data.shadows) && data.shadows.length) { const shadows = data.shadows; // ... }Also applies to: 101-115
108-110: Invalid CSS color in drop-shadow (rgba with 3 args).Same issue as in stvAPI: use rgb(...) or keep alpha.
- rgbaColor = rgbaColor.replace(/rgba\((\d+), (\d+), (\d+), (\d+(\.\d+)?)\)/, `rgba($1, $2, $3)`); + rgbaColor = rgbaColor.replace(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[^)]+\)/, `rgb($1, $2, $3)`);
545-554: Fix ARGB→RGBA channel order and alpha handling.Same bug here as stvAPI; colors will be wrong.
-const argbToRgba = (color) => { +const argbToRgba = (color) => { if (color < 0) { color = color >>> 0; } - - const red = (color >> 24) & 0xff; - const green = (color >> 16) & 0xff; - const blue = (color >> 8) & 0xff; - return `rgba(${red}, ${green}, ${blue}, 1)`; + const a = (color >>> 24) & 0xff; + const r = (color >>> 16) & 0xff; + const g = (color >>> 8) & 0xff; + const b = (color >>> 0) & 0xff; + const alpha = (a / 255).toFixed(3); + return `rgba(${r}, ${g}, ${b}, ${alpha})`; };utils/services/seventv/sharedStvWebSocket.js (5)
436-453: Fix exponential backoff: attempts never increment here → negative exponent, 500ms tight reconnect loopIncrement attempts in handleReconnection and ensure step ≥ 1. Also fix telemetry/logged attempt numbers.
handleReconnection() { - if (!this.shouldReconnect) { + if (!this.shouldReconnect) { console.log(`[Shared7TV]: Reconnection disabled`); try { window.app?.telemetry?.recordWebSocketEvent?.('7tv_shared', 'reconnection_disabled', { - attempt: this.reconnectAttempts + attempt: this.reconnectAttempts }); } catch (_) {} return; } - // exponential backoff: start * 2^(step-1) - // cap at maxRetrySteps, so after step 5 it stays at start * 2^(maxRetrySteps-1) - const step = Math.min(this.reconnectAttempts, this.maxRetrySteps); - const delay = this.startDelay * Math.pow(2, step - 1); + // exponential backoff: start * 2^(step-1); step starts at 1 + this.reconnectAttempts++; + const step = Math.min(this.reconnectAttempts, this.maxRetrySteps); + const delay = this.startDelay * Math.pow(2, Math.max(1, step) - 1); - console.log(`[Shared7TV]: Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts + 1})`); + console.log(`[Shared7TV]: Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`); try { window.app?.telemetry?.recordWebSocketEvent?.('7tv_shared', 'reconnection_scheduled', { - attempt: this.reconnectAttempts + 1, + attempt: this.reconnectAttempts, delay_ms: delay, step: step }); } catch (_) {} setTimeout(() => { this.connect(); }, delay); }
510-541: Subscribe user. for all unique STV user IDs, not just the first one*Current code subscribes once and misses user events for additional chatrooms. Iterate unique stvIds and drop the boolean gate.
async subscribeToUserEvents() { - if (this.userEventSubscribed || !this.chat || this.chat.readyState !== WebSocket.OPEN) { + if (!this.chat || this.chat.readyState !== WebSocket.OPEN) { return; } - // Find any chatroom with a valid 7TV user ID - const chatroomWithStvId = Array.from(this.chatrooms.values()).find(data => - data.stvId && data.stvId !== "0" && data.stvId !== INVALID_7TV_NULL_ID - ); - if (!chatroomWithStvId) { + const ids = Array.from(this.chatrooms.values()) + .map(d => d.stvId) + .filter(id => id && id !== "0" && id !== INVALID_7TV_NULL_ID); + const uniqueIds = [...new Set(ids)]; + if (uniqueIds.length === 0) { console.log(`[Shared7TV]: No valid 7TV user ID found for user events (skipping subscription)`); return; } - const eventKey = `user.*:${chatroomWithStvId.stvId}`; - if (this.subscribedEvents.has(eventKey)) { - return; - } - - const subscribeUserMessage = { - op: 35, - d: { - type: "user.*", - condition: { object_id: chatroomWithStvId.stvId }, - }, - }; - - this.chat.send(JSON.stringify(subscribeUserMessage)); - this.subscribedEvents.add(eventKey); - this.userEventSubscribed = true; - console.log(`[Shared7TV]: Subscribed to user.* events with stvId: ${chatroomWithStvId.stvId}`); + for (const stvId of uniqueIds) { + const eventKey = `user.*:${stvId}`; + if (this.subscribedEvents.has(eventKey)) continue; + const subscribeUserMessage = { + op: 35, + d: { type: "user.*", condition: { object_id: stvId } } + }; + this.chat.send(JSON.stringify(subscribeUserMessage)); + this.subscribedEvents.add(eventKey); + console.log(`[Shared7TV]: Subscribed to user.* events with stvId: ${stvId}`); + } + this.userEventSubscribed = this.subscribedEvents.size > 0; }
12-12: Make updateCosmetics synchronous and null-safe (stops/shadows may be undefined; unnecessary await causes race)Avoid async here and remove the await on a synchronous join; guard .stops/.shadows to prevent runtime errors.
-const updateCosmetics = async (body) => { +const updateCosmetics = (body) => { @@ - if (data.stops.length) { + if (Array.isArray(data?.stops) && data.stops.length) { @@ - if (data.shadows.length) { - const shadows = data.shadows; - - shadow = await shadows - .map((shadow) => { - let rgbaColor = argbToRgba(shadow.color); - - rgbaColor = rgbaColor.replace(/rgba\((\d+), (\d+), (\d+), (\d+(\.\d+)?)\)/, `rgba($1, $2, $3)`); - - return `drop-shadow(${rgbaColor} ${shadow.x_offset}px ${shadow.y_offset}px ${shadow.radius}px)`; - }) - .join(" "); - - push["shadows"] = shadow; - } + if (Array.isArray(data?.shadows) && data.shadows.length) { + const shadows = data.shadows; + const shadowCss = shadows + .map((s) => { + const rgba = argbToRgba(s.color); + const rgb = rgba.replace(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[^)]+\)/, "rgb($1, $2, $3)"); + return `drop-shadow(${rgb} ${s.x_offset}px ${s.y_offset}px ${s.radius}px)`; + }) + .join(" "); + push["shadows"] = shadowCss; + }Also applies to: 55-61, 105-119
938-947: ARGB → RGBA conversion is incorrect (channels shifted wrong); use alpha from ARGBCurrent code maps A→R, R→G, G→B, ignores B. Fix shifts and alpha normalization.
const argbToRgba = (color) => { if (color < 0) { color = color >>> 0; } - const red = (color >> 24) & 0xff; - const green = (color >> 16) & 0xff; - const blue = (color >> 8) & 0xff; - return `rgba(${red}, ${green}, ${blue}, 1)`; + const a = (color >> 24) & 0xff; // AA + const r = (color >> 16) & 0xff; // RR + const g = (color >> 8) & 0xff; // GG + const b = color & 0xff; // BB + const alpha = +(a / 255).toFixed(3); + return `rgba(${r}, ${g}, ${b}, ${alpha})`; };
465-498: Channel-scoped cosmetic/entitlement subscriptions likely miss user-scoped events7TV emits cosmetics/entitlements tied to users; channel ctx often won’t deliver expected events. Add user-scoped subscriptions (by stvId) alongside channel ones.
Example addition (outside this hunk):
async subscribeToUserScopedCosmeticAndEntitlementEvents(stvId) { if (!this.chat || this.chat.readyState !== WebSocket.OPEN) return; for (const type of ["cosmetic.*", "entitlement.*"]) { const key = `${type}:user:${stvId}`; if (this.subscribedEvents.has(key)) continue; this.chat.send(JSON.stringify({ op: 35, d: { type, condition: { object_id: stvId } } })); this.subscribedEvents.add(key); console.log(`[Shared7TV]: Subscribed to ${type} for stvUser ${stvId}`); } }Then call it from subscribeToChatroomEvents when stvId is valid:
const hasValidStvId = chatroomData.stvId && chatroomData.stvId !== "0" && chatroomData.stvId !== INVALID_7TV_NULL_ID; if (hasValidStvId) await this.subscribeToUserScopedCosmeticAndEntitlementEvents(chatroomData.stvId);
🧹 Nitpick comments (14)
src/renderer/src/components/Messages/ModActionMessage.jsx (1)
15-44: Missing dependency in useCallback.userChatroomInfo is referenced but not in deps; could capture stale value.
- ); + ); // ... - [chatroomName, username, chatroomId, allStvEmotes, subscriberBadges], + [chatroomName, username, chatroomId, allStvEmotes, subscriberBadges, userChatroomInfo],src/renderer/src/components/Messages/Message.jsx (1)
44-55: Avoid high-frequency getUserStyle calls (noise + cost).This selector calls a noisy getter every render; it warns if called <100ms apart. Derive from state directly to avoid logs, or gate logs behind a debug flag.
- const subscribedUserStyle = useCosmeticsStore( - useShallow((state) => { - if (!message?.sender || type === "replyThread" || type === "dialog") { - return null; - } - return state.getUserStyle(message.sender.username); - }), - ); + const subscribedUserStyle = useCosmeticsStore(useShallow((state) => { + if (!message?.sender || type === "replyThread" || type === "dialog") return null; + const uname = message.sender.username?.toLowerCase()?.replaceAll('-', '_'); + const us = state.userStyles[uname]; + if (!us?.badgeId && !us?.paintId) return null; + const badge = state.globalCosmetics.badges?.find((b) => b.id === us.badgeId); + const paint = state.globalCosmetics.paints?.find((p) => p.id === us.paintId); + return { badge, paint, color: us.color, username: us.username }; + }));utils/services/seventv/stvAPI.js (2)
254-356: Function name and unused param.This fetches a global catalog and ignores channelId. Either rename (e.g., getCosmeticsCatalog) or document and remove the param.
Would you prefer we rename the function and update preload/ChatProvider accordingly?
260-265: Add request timeout and basic retry.Network call should have a timeout to avoid hanging.
- const response = await axios.get(`https://api.7tv.app/v2/cosmetics`); + const response = await axios.get(`https://api.7tv.app/v2/cosmetics`, { timeout: 10000 });src/renderer/src/providers/CosmeticsProvider.jsx (2)
61-63: Connection discriminator mismatch ('type' vs 'platform').Elsewhere you check c.platform === "KICK". Use the same key here.
- kickConnection: userStyle.connections?.find((c) => c.type === "KICK"), + kickConnection: userStyle.connections?.find((c) => c.platform === "KICK"),
11-21: Gate verbose diagnostics behind a debug flag.Logs are very chatty and executed in hot paths; wrap with a feature flag (e.g., window.KT_TELEMETRY_UTILS?.shouldLogDebug?.()).
Also applies to: 22-31, 35-45, 51-53, 170-179, 188-196, 205-213, 215-220, 228-234, 257-279, 305-308
src/renderer/src/providers/ChatProvider.jsx (1)
555-557: Gate debug logs.Use your telemetry debug flag for these presence logs to keep consoles clean.
- if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) { + if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) { console.log("[7tv Presence]: Last update time for chatroom:", userId, lastUpdateTime, stvPresenceUpdates); }- if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) { + if (window.__KT_TELEMETRY_UTILS__?.shouldLogDebug?.()) { console.log("[7tv Presence]: Sending presence check for chatroom:", chatroom.streamerData.user_id); }Also applies to: 4131-4133
utils/services/seventv/sharedStvWebSocket.js (7)
213-218: Also (re)subscribe user. when adding a chatroom on an already-open socket*Without this, user events for newly added chatrooms won't be subscribed.
if (this.connectionState === 'connected') { console.log(`[Shared7TV DIAGNOSTIC]: Subscribing to events for new chatroom ${chatroomId}`); this.subscribeToChatroomEvents(chatroomId); + // ensure user-scoped subscriptions are up to date + this.subscribeToUserEvents(); } else {
31-37: Badge URL construction lacks host/files guards; can throw if host/files missingMake it resilient to missing host or empty files array.
- const newBadge = { - id: data.id === "00000000000000000000000000" ? data.ref_id || "default_id" : data.id, - title: data.tooltip, - url: `https:${data.host.url}/${data.host.files[data.host.files.length - 1].name}`, - }; + const id = data.id === "00000000000000000000000000" ? (data.ref_id || "default_id") : data.id; + const baseUrl = typeof data?.host?.url === "string" + ? (data.host.url.startsWith("//") ? `https:${data.host.url}` : data.host.url) + : null; + const lastFile = Array.isArray(data?.host?.files) && data.host.files.length + ? data.host.files[data.host.files.length - 1].name + : null; + const newBadge = { + id, + title: data.tooltip, + url: baseUrl && lastFile ? `${baseUrl}/${lastFile}` : null, + };
824-837: Check entitlement body shape before filtering by kindSome 7TV entitlement events carry kind under body.object.kind; guard both to avoid dropping valid events.
- // Process entitlements for both cosmetics (kind 10) and emote sets (kind 5) - if (body.kind === 10 || body.kind === 5) { + // Process entitlements for both cosmetics (kind 10) and emote sets (kind 5) + const kind = body?.kind ?? body?.object?.kind; + if (kind === 10 || kind === 5) {
381-395: Gate verbose console logs behind a debug flag to reduce prod noiseKeep diagnostics, but guard with a single isDebug flag or telemetry-only in production.
- console.log(`[Shared7TV]: ...`); + if (window.__DEBUG_FLAGS__?.stv) console.log(`[Shared7TV]: ...`);Also applies to: 397-400, 465-498, 542-569, 571-598, 633-757, 121-123, 37-37, 215-218
767-771: Avoid logging PII (usernames) unless gatedUsernames can be considered PII; ensure debug-only.
Also applies to: 775-782
173-204: Store STV user ID variant for routing (consistency with idVariants block)Add stvId into idVariants to improve mapping.
if (chatroomData) { storedData.idVariants = { chatroom_id: String(chatroomId), streamer_id: String(channelKickID), streamer_user_id: chatroomData.streamerData?.user_id ? String(chatroomData.streamerData.user_id) : null, username: chatroomData.streamerData?.username || null, user_username: chatroomData.streamerData?.user?.username || null, slug: chatroomData.slug || null, + stv_user_id: storedData.stvId || null, };
865-872: Emote set routing: body.id might be nested; add fallback to body.object.id/object_idPrevents misrouting when the event schema differs.
- if (type.startsWith("emote_set.") && body?.id) { + if (type.startsWith("emote_set.") && (body?.id || body?.object_id || body?.object?.id)) { for (const [chatroomId, data] of this.chatrooms) { - if (data.stvEmoteSetId === body.id) { + const incomingId = body?.id ?? body?.object_id ?? body?.object?.id; + if (data.stvEmoteSetId === incomingId) { return chatroomId; } } }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (9)
src/preload/index.js(2 hunks)src/renderer/src/components/Messages/Message.jsx(2 hunks)src/renderer/src/components/Messages/ModActionMessage.jsx(1 hunks)src/renderer/src/providers/ChatProvider.jsx(7 hunks)src/renderer/src/providers/CosmeticsProvider.jsx(4 hunks)utils/services/connectionManager.js(4 hunks)utils/services/seventv/sharedStvWebSocket.js(14 hunks)utils/services/seventv/stvAPI.js(1 hunks)utils/services/seventv/stvWebsocket.js(4 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**
📄 CodeRabbit inference engine (AGENTS.md)
**: Use electron-vite + React conventions instead of raw Electron patterns
When unsure, consult electron-vite/Electron/Vite docs (Context7) to confirm patterns
Omit empty sections in release notes
Files:
utils/services/seventv/stvAPI.jssrc/preload/index.jssrc/renderer/src/providers/CosmeticsProvider.jsxsrc/renderer/src/components/Messages/Message.jsxsrc/renderer/src/components/Messages/ModActionMessage.jsxutils/services/seventv/sharedStvWebSocket.jsutils/services/seventv/stvWebsocket.jssrc/renderer/src/providers/ChatProvider.jsxutils/services/connectionManager.js
!dist/**
📄 CodeRabbit inference engine (AGENTS.md)
Do not commit built installers/artifacts in dist/
Files:
utils/services/seventv/stvAPI.jssrc/preload/index.jssrc/renderer/src/providers/CosmeticsProvider.jsxsrc/renderer/src/components/Messages/Message.jsxsrc/renderer/src/components/Messages/ModActionMessage.jsxutils/services/seventv/sharedStvWebSocket.jsutils/services/seventv/stvWebsocket.jssrc/renderer/src/providers/ChatProvider.jsxutils/services/connectionManager.js
src/{renderer/src,preload}/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
Resolve static assets via Vite URLs (new URL('./asset', import.meta.url))
Files:
src/preload/index.jssrc/renderer/src/providers/CosmeticsProvider.jsxsrc/renderer/src/components/Messages/Message.jsxsrc/renderer/src/components/Messages/ModActionMessage.jsxsrc/renderer/src/providers/ChatProvider.jsx
src/preload/**/*.{ts,js,mts,mjs,cjs}
📄 CodeRabbit inference engine (AGENTS.md)
src/preload/**/*.{ts,js,mts,mjs,cjs}: Expose a minimal, purpose-built API from preload via contextBridge.exposeInMainWorld
Do not expose raw Node objects/APIs from preload
Use import.meta.env.PRELOAD_VITE_* for preload-scoped config
Files:
src/preload/index.js
src/{main,preload,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}
📄 CodeRabbit inference engine (AGENTS.md)
Use shared non-secret variables via import.meta.env.VITE_*
Files:
src/preload/index.jssrc/renderer/src/providers/CosmeticsProvider.jsxsrc/renderer/src/components/Messages/Message.jsxsrc/renderer/src/components/Messages/ModActionMessage.jsxsrc/renderer/src/providers/ChatProvider.jsx
src/renderer/src/**/*.{ts,tsx,js,jsx}
📄 CodeRabbit inference engine (AGENTS.md)
src/renderer/src/**/*.{ts,tsx,js,jsx}: Do not use direct Node APIs in the renderer
Use ipcRenderer.invoke('channel', payload) for request/response from renderer
Use import.meta.env.RENDERER_VITE_* for renderer-scoped config
Avoid bare KT_* env vars in the renderer; use RENDERER_VITE_KT_* instead
Files:
src/renderer/src/providers/CosmeticsProvider.jsxsrc/renderer/src/components/Messages/Message.jsxsrc/renderer/src/components/Messages/ModActionMessage.jsxsrc/renderer/src/providers/ChatProvider.jsx
src/{main,renderer/src}/**/*.{ts,tsx,js,jsx,mts,mjs,cjs}
📄 CodeRabbit inference engine (AGENTS.md)
For fire-and-forget events, use ipcRenderer.send and ipcMain.on with namespaced channels like 'app:settings:get'
Files:
src/renderer/src/providers/CosmeticsProvider.jsxsrc/renderer/src/components/Messages/Message.jsxsrc/renderer/src/components/Messages/ModActionMessage.jsxsrc/renderer/src/providers/ChatProvider.jsx
🧠 Learnings (1)
📚 Learning: 2025-09-18T22:47:22.731Z
Learnt from: CR
PR: BP602/KickTalk#0
File: AGENTS.md:0-0
Timestamp: 2025-09-18T22:47:22.731Z
Learning: Applies to src/preload/**/*.{ts,js,mts,mjs,cjs} : Expose a minimal, purpose-built API from preload via contextBridge.exposeInMainWorld
Applied to files:
src/preload/index.js
🧬 Code graph analysis (7)
utils/services/seventv/stvAPI.js (2)
utils/services/seventv/sharedStvWebSocket.js (10)
cosmetics(4-7)randomColor(51-51)normalizedColors(56-59)gradient(61-61)argbToRgba(938-947)isDeg_or_Shape(69-69)shadow(103-103)red(943-943)green(944-944)blue(945-945)utils/services/seventv/stvWebsocket.js (10)
cosmetics(3-6)randomColor(47-47)normalizedColors(52-55)gradient(57-57)argbToRgba(545-554)isDeg_or_Shape(65-65)shadow(99-99)red(550-550)green(551-551)blue(552-552)
src/renderer/src/providers/CosmeticsProvider.jsx (2)
utils/services/seventv/sharedStvWebSocket.js (1)
body(17-17)utils/services/seventv/stvWebsocket.js (1)
body(13-13)
src/renderer/src/components/Messages/Message.jsx (2)
src/renderer/src/providers/CosmeticsProvider.jsx (1)
useCosmeticsStore(3-309)src/renderer/src/components/Dialogs/User.jsx (1)
dialogUserStyle(21-21)
src/renderer/src/components/Messages/ModActionMessage.jsx (2)
src/renderer/src/components/Messages/Message.jsx (1)
userStyle(54-54)src/renderer/src/providers/CosmeticsProvider.jsx (1)
useCosmeticsStore(3-309)
utils/services/seventv/sharedStvWebSocket.js (2)
utils/services/seventv/stvAPI.js (1)
cosmetics(268-268)utils/services/seventv/stvWebsocket.js (3)
cosmetics(3-6)push(49-49)body(13-13)
src/renderer/src/providers/ChatProvider.jsx (1)
src/renderer/src/providers/CosmeticsProvider.jsx (1)
useCosmeticsStore(3-309)
utils/services/connectionManager.js (2)
src/renderer/src/components/Chat/index.jsx (1)
chatroom(17-17)src/renderer/src/components/Chat/Input/index.jsx (1)
chatroom(1166-1166)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: test-builds (windows-latest)
- GitHub Check: test-builds (macos-latest)
🔇 Additional comments (10)
src/renderer/src/components/Messages/Message.jsx (1)
7-7: LGTM: correct import path for useShallow.src/preload/index.js (1)
38-38: LGTM: minimal, purpose-built preload API exposure (stv.getChannelCosmetics).Matches the preload guideline; no raw Node/Electron objects exposed.
Also applies to: 498-502
utils/services/connectionManager.js (1)
121-127: LGTM: explicit handler registration logs help traceability.src/renderer/src/providers/ChatProvider.jsx (2)
1230-1246: LGTM: initial cosmetics load via preload STV API.Good guarding and store update.
1961-1975: Duplicate normalization logic diverges from store.Here you normalize with '-'→'_' + toLowerCase(); align with CosmeticsProvider (or centralize a helper) to avoid future drift.
Option A: keep normalization here and update CosmeticsProvider (see related comment).
Option B: create utils/normalizeUsername and use it in both places.Also applies to: 1981-1997
src/renderer/src/components/Messages/ModActionMessage.jsx (1)
21-21: Normalize username before cosmetics lookup (hyphenated names break).ChatProvider writes styles under username.replaceAll('-', '_').toLowerCase(), but getUserStyle here uses the raw username. Hyphenated users won’t resolve styles. Either centralize a normalizer or normalize here.
- const userStyle = useCosmeticsStore.getState().getUserStyle(usernameDialog); + const normalized = usernameDialog?.replaceAll('-', '_').toLowerCase(); + const userStyle = useCosmeticsStore.getState().getUserStyle(normalized);src/renderer/src/providers/CosmeticsProvider.jsx (1)
32-34: Unify username normalization (reader vs writer).Store uses toLowerCase(); ChatProvider writes with '-'→'_' + toLowerCase(). Normalize identically here to avoid cache misses.
- const transformedUsername = username.toLowerCase(); + const transformedUsername = username?.toLowerCase()?.replaceAll('-', '_');- const transformedUsername = username.toLowerCase(); + const transformedUsername = username?.toLowerCase()?.replaceAll('-', '_');Also applies to: 149-156
utils/services/seventv/stvAPI.js (1)
270-279: No change required — v2 badges useurlsarrayVerified: the legacy /v2/cosmetics response returns badges[].urls (e.g., ["1","https://cdn.7tv.app/.../1x", ""]), so the mapping in utils/services/seventv/stvAPI.js (lines 270–279) is correct; if you migrate to newer Event/API endpoints they may use host/files.
utils/services/seventv/sharedStvWebSocket.js (2)
810-822: If updateCosmetics stays async, await it before dispatching; else keep it sync as suggested aboveEnsures shadows/styles are fully computed before broadcasting.
- case "cosmetic.create": - updateCosmetics(body); + case "cosmetic.create": + // If updateCosmetics remains async, await it: + await updateCosmetics(body);If you adopt the synchronous refactor above, ignore this change.
201-212: No action required — addChatroom call sites are compatible with the new signatureRepo search shows sharedStvWebSocket.addChatroom is only invoked from utils/services/connectionManager.js (calls at lines ~319 and ~603); sharedStvWebSocket.addChatroom declares defaults for stvId, stvEmoteSetId, and chatroomData, so existing calls are compatible — no arity mismatches found.
| // Expose test function globally for debugging | ||
| if (typeof window !== 'undefined') { | ||
| window.testCosmeticsStore = () => { | ||
| console.log(`[TEST] Starting cosmetics test...`); | ||
| const store = useCosmeticsStore.getState(); | ||
| console.log(`[TEST] Got store:`, !!store); | ||
| store.testCosmetics(); | ||
|
|
||
| // Also test retrieval | ||
| setTimeout(() => { | ||
| const result = store.getUserStyle('testuser'); | ||
| console.log(`[CosmeticsStore TEST] getUserStyle result:`, result); | ||
| }, 100); | ||
| }; | ||
|
|
||
| // Also expose the store itself for direct testing | ||
| window.cosmeticsStore = useCosmeticsStore; | ||
| console.log(`[CosmeticsProvider] Exposed window.testCosmeticsStore() and window.cosmeticsStore`); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don’t expose debug hooks in production.
window.testCosmeticsStore and window.cosmeticsStore should be dev-only.
-if (typeof window !== 'undefined') {
+if (typeof window !== 'undefined' && import.meta.env.DEV) {📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| // Expose test function globally for debugging | |
| if (typeof window !== 'undefined') { | |
| window.testCosmeticsStore = () => { | |
| console.log(`[TEST] Starting cosmetics test...`); | |
| const store = useCosmeticsStore.getState(); | |
| console.log(`[TEST] Got store:`, !!store); | |
| store.testCosmetics(); | |
| // Also test retrieval | |
| setTimeout(() => { | |
| const result = store.getUserStyle('testuser'); | |
| console.log(`[CosmeticsStore TEST] getUserStyle result:`, result); | |
| }, 100); | |
| }; | |
| // Also expose the store itself for direct testing | |
| window.cosmeticsStore = useCosmeticsStore; | |
| console.log(`[CosmeticsProvider] Exposed window.testCosmeticsStore() and window.cosmeticsStore`); | |
| } | |
| // Expose test function globally for debugging | |
| if (typeof window !== 'undefined' && import.meta.env.DEV) { | |
| window.testCosmeticsStore = () => { | |
| console.log(`[TEST] Starting cosmetics test...`); | |
| const store = useCosmeticsStore.getState(); | |
| console.log(`[TEST] Got store:`, !!store); | |
| store.testCosmetics(); | |
| // Also test retrieval | |
| setTimeout(() => { | |
| const result = store.getUserStyle('testuser'); | |
| console.log(`[CosmeticsStore TEST] getUserStyle result:`, result); | |
| }, 100); | |
| }; | |
| // Also expose the store itself for direct testing | |
| window.cosmeticsStore = useCosmeticsStore; | |
| console.log(`[CosmeticsProvider] Exposed window.testCosmeticsStore() and window.cosmeticsStore`); | |
| } |
🤖 Prompt for AI Agents
In src/renderer/src/providers/CosmeticsProvider.jsx around lines 311 to 329, the
debug helpers are being attached to window unconditionally; restrict these to
non-production builds by wrapping the exposure in a development-only check (e.g.
process.env.NODE_ENV !== 'production' or your app's equivalent like
import.meta.env.DEV), only define window.testCosmeticsStore and
window.cosmeticsStore inside that guard, and remove or reduce console noise if
desired; ensure bundlers/tree-shakers will drop this block in production by
using the canonical env variable your build uses.
| const shadow = paint.data.shadows | ||
| .map((shadow) => { | ||
| let rgbaColor = argbToRgba(shadow.color); | ||
| rgbaColor = rgbaColor.replace(/rgba\((\d+), (\d+), (\d+), (\d+(\.\d+)?)\)/, `rgba($1, $2, $3)`); | ||
| return `drop-shadow(${rgbaColor} ${shadow.x_offset}px ${shadow.y_offset}px ${shadow.radius}px)`; | ||
| }) | ||
| .join(" "); | ||
| paintObject.shadows = shadow; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Invalid CSS color in drop-shadow (rgba with 3 args).
The replace produces rgba(r,g,b) which is invalid; use rgb(...) or keep alpha.
- let rgbaColor = argbToRgba(shadow.color);
- rgbaColor = rgbaColor.replace(/rgba\((\d+), (\d+), (\d+), (\d+(\.\d+)?)\)/, `rgba($1, $2, $3)`);
- return `drop-shadow(${rgbaColor} ${shadow.x_offset}px ${shadow.y_offset}px ${shadow.radius}px)`;
+ const rgbaColor = argbToRgba(shadow.color);
+ const rgbColor = rgbaColor.replace(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[^)]+\)/, `rgb($1, $2, $3)`);
+ return `drop-shadow(${rgbColor} ${shadow.x_offset}px ${shadow.y_offset}px ${shadow.radius}px)`;📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const shadow = paint.data.shadows | |
| .map((shadow) => { | |
| let rgbaColor = argbToRgba(shadow.color); | |
| rgbaColor = rgbaColor.replace(/rgba\((\d+), (\d+), (\d+), (\d+(\.\d+)?)\)/, `rgba($1, $2, $3)`); | |
| return `drop-shadow(${rgbaColor} ${shadow.x_offset}px ${shadow.y_offset}px ${shadow.radius}px)`; | |
| }) | |
| .join(" "); | |
| paintObject.shadows = shadow; | |
| } | |
| const shadow = paint.data.shadows | |
| .map((shadow) => { | |
| const rgbaColor = argbToRgba(shadow.color); | |
| const rgbColor = rgbaColor.replace(/rgba\((\d+),\s*(\d+),\s*(\d+),\s*[^)]+\)/, `rgb($1, $2, $3)`); | |
| return `drop-shadow(${rgbColor} ${shadow.x_offset}px ${shadow.y_offset}px ${shadow.radius}px)`; | |
| }) | |
| .join(" "); | |
| paintObject.shadows = shadow; |
🤖 Prompt for AI Agents
In utils/services/seventv/stvAPI.js around lines 331 to 339, the current replace
turns rgba(...) with 4 args into an invalid rgba(r,g,b) 3-arg string; change the
logic to parse the rgba capture groups and output either rgb(r,g,b) when alpha
is 1 (or absent) or a proper rgba(r,g,b,a) when alpha < 1, i.e. extract the
captured r,g,b,a from the regex and conditionally format the color string
accordingly before building the drop-shadow so the resulting CSS color is valid.
| const argbToRgba = (color) => { | ||
| if (color < 0) { | ||
| color = color >>> 0; | ||
| } | ||
|
|
||
| const red = (color >> 24) & 0xff; | ||
| const green = (color >> 16) & 0xff; | ||
| const blue = (color >> 8) & 0xff; | ||
| return `rgba(${red}, ${green}, ${blue}, 1)`; | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ARGB→RGBA channel order is wrong and alpha is dropped.
Current shifts yield A,R,G as R,G,B and hardcode alpha=1. Fix bit extraction and preserve alpha.
-const argbToRgba = (color) => {
+const argbToRgba = (color) => {
if (color < 0) {
color = color >>> 0;
}
-
- const red = (color >> 24) & 0xff;
- const green = (color >> 16) & 0xff;
- const blue = (color >> 8) & 0xff;
- return `rgba(${red}, ${green}, ${blue}, 1)`;
+ const a = (color >>> 24) & 0xff;
+ const r = (color >>> 16) & 0xff;
+ const g = (color >>> 8) & 0xff;
+ const b = (color >>> 0) & 0xff;
+ const alpha = (a / 255).toFixed(3);
+ return `rgba(${r}, ${g}, ${b}, ${alpha})`;
};📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const argbToRgba = (color) => { | |
| if (color < 0) { | |
| color = color >>> 0; | |
| } | |
| const red = (color >> 24) & 0xff; | |
| const green = (color >> 16) & 0xff; | |
| const blue = (color >> 8) & 0xff; | |
| return `rgba(${red}, ${green}, ${blue}, 1)`; | |
| }; | |
| const argbToRgba = (color) => { | |
| if (color < 0) { | |
| color = color >>> 0; | |
| } | |
| const a = (color >>> 24) & 0xff; | |
| const r = (color >>> 16) & 0xff; | |
| const g = (color >>> 8) & 0xff; | |
| const b = (color >>> 0) & 0xff; | |
| const alpha = (a / 255).toFixed(3); | |
| return `rgba(${r}, ${g}, ${b}, ${alpha})`; | |
| }; |
🤖 Prompt for AI Agents
In utils/services/seventv/stvAPI.js around lines 359 to 368, the ARGB→RGBA
conversion currently extracts the wrong channels and forces alpha to 1; update
the bit extraction so alpha = (color >>> 24) & 0xff, red = (color >>> 16) &
0xff, green = (color >>> 8) & 0xff, blue = color & 0xff, and return the string
with the preserved alpha as a normalized fraction (alpha / 255) in the rgba(...)
result; keep the initial unsigned conversion (color = color >>> 0) and use
unsigned right shifts (>>>) for all extractions.
Summary
Testing
https://chatgpt.com/codex/tasks/task_e_68cfcfb0a35c833190e666f945a5bba7
Summary by CodeRabbit